import mx.core.UIObject;
import mx.utils.Delegate;

import com.mosesSupposes.fuse.PennerEasing;

import flash.geom.Point;

import gugga.animations.IAnimation;
import gugga.animations.MaskAnimationTypes;
import gugga.animations.MotionDirections;
import gugga.animations.MotionTypes;
import gugga.animations.PropertiesTweenAnimation;
import gugga.debug.Assertion;
import gugga.events.EventDispatcher;
import gugga.utils.DrawUtil;
import gugga.utils.EventRethrower;

/**
 * @author Barni
 */
class gugga.animations.MaskAnimation extends EventDispatcher implements IAnimation 
{
	private static var MASK_BOUNDARIES_PATCH : Number = 5;
	
	private static var MASK_INCREMENT:Number = 0;
	
	private var mType : MaskAnimationTypes;
	public function get Type() : MaskAnimationTypes { return mType; }
	public function set Type(aValue : MaskAnimationTypes) : Void { mType = aValue; }
	
	private var mTarget : MovieClip;
	public function get Target() : MovieClip { return mTarget; }
	public function set Target(aValue : MovieClip) : Void { mTarget = aValue; }
	
	private var mTime : Number = 1;
	public function get Time() : Number { return mTime; }
	public function set Time(aValue : Number) : Void { mTime = aValue; }
	
	private var mEasingType : Function = PennerEasing.linear;
	public function get EasingType() : Function { return mEasingType; }
	public function set EasingType(aValue : Function) : Void { mEasingType = aValue; }
	
	private var mDirection : MotionDirections;
	public function get Direction() : MotionDirections { return mDirection; }
	public function set Direction(aValue : MotionDirections) : Void { mDirection = aValue; }
	
	private var mMotionType : MotionTypes;
	public function get MotionType() : MotionTypes { return mMotionType; }
	public function set MotionType(aValue : MotionTypes) : Void { mMotionType = aValue; }
	
	private var mAnimation : PropertiesTweenAnimation;
	private var mMask : MovieClip;
	
	private function MaskAnimation(aTarget : MovieClip, aType : MaskAnimationTypes, 
		aDirection : MotionDirections, aMotionType : MotionTypes) 
	{
		super();
		
		mTarget = aTarget;
		mType = aType;
		mDirection = aDirection;
		mMotionType = aMotionType;
		
		mAnimation = new PropertiesTweenAnimation();
		mAnimation.addEventListener("completed", Delegate.create(this, onCompleted));
		
		EventRethrower.create(this, mAnimation, "start");
		EventRethrower.create(this, mAnimation, "interrupted");
	}

	public function addCuePoint(aPosition : Number, aEventName : String) : Void 
	{
		mAnimation.addCuePoint(aPosition, aEventName);
		EventRethrower.create(this, mAnimation, aEventName);
	}

	public function getCuePointEventNames() : Array 
	{
		return mAnimation.getCuePointEventNames();
	}

	public function start() : Void 
	{
		createMask();
		
		var clip : MovieClip;
		
		switch(mType)
		{
			case MaskAnimationTypes.Fly:
				clip = mTarget;
				break;
			case MaskAnimationTypes.Wipe:
				clip = mMask;
				break;
			default:
				Assertion.fail("Unexpected type.", this, arguments);
				break;
		}
		
		configureAnimation(clip);
		
		if(mTarget instanceof UIObject)
		{
			mTarget.visible = true;
		}
		else
		{
			mTarget._visible = true;
		}
		
		mAnimation.start();
	}

	public function isRunning() : Boolean 
	{
		return mAnimation.isRunning();
	}

	public function isImmediatelyInterruptable() : Boolean 
	{
		return mAnimation.isImmediatelyInterruptable();
	}

	public function interrupt() : Void 
	{
		mAnimation.interrupt();
		dispose();
	}
	
	public function dispose() : Void 
	{
		mTarget.setMask(null);
		mMask.removeMovieClip();
	}
	
	private function configureAnimation(aClip : MovieClip) : Void
	{
		var startingPosition : Point = getStartingPosition(aClip, mDirection, mMotionType);
		var finalPosition : Point = getFinalPosition(aClip, mDirection, mMotionType);
		
		mAnimation.setTween(aClip, ["_x", "_y"], [finalPosition.x, finalPosition.y], mTime, mEasingType);
		
		aClip._x = startingPosition.x;
		aClip._y = startingPosition.y;
	}
	
	private function createMask() : Void 
	{
		/**
		 * Mask increment is added in order to assure with full confidence 
		 * that the newly created mask instance name is unique. 
		 * 
		 * The version without this Mask increment was causing bugs in cases 
		 * of consequent playing of mask animations on a single movie clip.
		 * 
		 * The bug actually was caused by setting masks with the same instance 
		 * name, no mather of whether this masks have already been disposed. 
		 */
		MASK_INCREMENT++;
		var maskInstanceName = "mask_" + mTarget._name + MASK_INCREMENT;
		
		var targetContainer : MovieClip = mTarget._parent;
		
		mMask = targetContainer.createEmptyMovieClip(maskInstanceName, 
			targetContainer.getNextHighestDepth());
		
		DrawUtil.fillRect(mMask, 0xFF0000, 100, 0, 0, 1, 1);

		if(mTarget instanceof UIObject)
		{
			mMask._x = mTarget.x - MASK_BOUNDARIES_PATCH;
			mMask._y = mTarget.y - MASK_BOUNDARIES_PATCH;
			mMask._width = mTarget.width + 2 * MASK_BOUNDARIES_PATCH;
			mMask._height= mTarget.height + 2 * MASK_BOUNDARIES_PATCH;
		}
		else
		{
			mMask._x = mTarget._x - MASK_BOUNDARIES_PATCH;
			mMask._y = mTarget._y - MASK_BOUNDARIES_PATCH;
			mMask._width = mTarget._width + 2 * MASK_BOUNDARIES_PATCH;
			mMask._height= mTarget._height + 2 * MASK_BOUNDARIES_PATCH;
		}
		
		//mMask.cacheAsBitmap = true;
		mTarget.setMask(mMask);
	}
	
	private function getFinalPosition(aClip : MovieClip) : Point 
	{
		var result : Point;
		if(mMotionType == MotionTypes.Out)
		{
			result = calculateDirectionBasedOffset(aClip, mDirection);
		}
		else
		{
			result = new Point(aClip._x, aClip._y);
		}
		return result;
	}

	private function getStartingPosition(aClip : MovieClip) : Point 
	{
		var result : Point;
		if(mMotionType == MotionTypes.In)
		{
			result = calculateDirectionBasedOffset(aClip, mDirection);
		}
		else
		{
			result = new Point(aClip._x, aClip._y);
		}
		return result;
	}
	
	private function calculateDirectionBasedOffset(aClip : MovieClip, 
		aDirection : MotionDirections) : Point
	{
		var result : Point = new Point(aClip._x, aClip._y);
		switch(aDirection)
		{
			case MotionDirections.Up:
			case MotionDirections.UpLeft:
			case MotionDirections.UpRight:
				
				result.y -= aClip._height;
				break;
			
			
			case MotionDirections.Down:
			case MotionDirections.DownLeft:
			case MotionDirections.DownRight:
			
				result.y += aClip._height;
				break;
				
				
			case MotionDirections.Left:
			case MotionDirections.UpLeft:
			case MotionDirections.DownLeft:
				
				result.x -= aClip._width;
				break;
				
				
			case MotionDirections.Right:
			case MotionDirections.UpRight:
			case MotionDirections.DownRight:
				
				result.x += aClip._width;
				break;
			
			case MotionDirections.None:
				break;
				
			default:
				Assertion.fail("Unexpected direction: " + aDirection, this, arguments);
				break;
		}
		return result;
	}
	
	private function onCompleted() : Void 
	{
		dispose();
		dispatchEvent({type : "completed", target : this});	
	}
	
	public static function createWipeInAnimation(aDirection : MotionDirections, 
		aTarget : MovieClip, aTime : Number, aEasingType : Function, 
		aDelay : Number) : MaskAnimation
	{
		var result : MaskAnimation = new MaskAnimation(aTarget, MaskAnimationTypes.Wipe,
			aDirection, MotionTypes.In);
		
		applyAdditionalProperties(result, aTime, aEasingType, aDelay);
		
		return result;
	}
	
	public static function createWipeOutAnimation(aDirection : MotionDirections, 
		aTarget : MovieClip, aTime : Number, aEasingType : Function, 
		aDelay : Number) : MaskAnimation
	{
		var result : MaskAnimation = new MaskAnimation(aTarget, MaskAnimationTypes.Wipe,
			aDirection, MotionTypes.Out);
		
		applyAdditionalProperties(result, aTime, aEasingType, aDelay);
		
		return result;
	}
	
	public static function createFlyInAnimation(aDirection : MotionDirections, 
		aTarget : MovieClip, aTime : Number, aEasingType : Function, 
		aDelay : Number) : MaskAnimation
	{
		var result : MaskAnimation = new MaskAnimation(aTarget, MaskAnimationTypes.Fly,
			aDirection, MotionTypes.In);
		
		applyAdditionalProperties(result, aTime, aEasingType, aDelay);
		
		return result;
	}
	
	public static function createFlyOutAnimation(aDirection : MotionDirections, 
		aTarget : MovieClip, aTime : Number, aEasingType : Function, 
		aDelay : Number) : MaskAnimation
	{
		var result : MaskAnimation = new MaskAnimation(aTarget, MaskAnimationTypes.Fly,
			aDirection, MotionTypes.Out);
		
		applyAdditionalProperties(result, aTime, aEasingType, aDelay);
		
		return result;
	}
	
	private static function applyAdditionalProperties(aAnimation : MaskAnimation, aTime : Number, 
		aEasingType : Function) : Void
	{
		if(aTime)
		{
			aAnimation.Time = aTime;
		}
		
		if(aEasingType)
		{
			aAnimation.EasingType = aEasingType;
		}
	}
}